/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
**********/
// "liveMedia"
// Copyright (c) 1996-2018 Live Networks, Inc.  All rights reserved.
// RTP sink for Vorbis audio
// Implementation

#include "include/VorbisAudioRTPSink.hh"
#include "include/Base64.hh"
#include "include/VorbisAudioRTPSource.hh" // for parseVorbisOrTheoraConfigStr()

VorbisAudioRTPSink *VorbisAudioRTPSink
::createNew(UsageEnvironment &env, Groupsock *RTPgs,
            u_int8_t rtpPayloadFormat, u_int32_t rtpTimestampFrequency, unsigned numChannels,
            u_int8_t *identificationHeader, unsigned identificationHeaderSize,
            u_int8_t *commentHeader, unsigned commentHeaderSize,
            u_int8_t *setupHeader, unsigned setupHeaderSize,
            u_int32_t identField) {
    return new VorbisAudioRTPSink(env, RTPgs,
                                  rtpPayloadFormat, rtpTimestampFrequency, numChannels,
                                  identificationHeader, identificationHeaderSize,
                                  commentHeader, commentHeaderSize,
                                  setupHeader, setupHeaderSize,
                                  identField);
}

VorbisAudioRTPSink *VorbisAudioRTPSink
::createNew(UsageEnvironment &env, Groupsock *RTPgs, u_int8_t rtpPayloadFormat,
            u_int32_t rtpTimestampFrequency, unsigned numChannels,
            char const *configStr) {
    // Begin by decoding and unpacking the configuration string:
    u_int8_t *identificationHeader;
    unsigned identificationHeaderSize;
    u_int8_t *commentHeader;
    unsigned commentHeaderSize;
    u_int8_t *setupHeader;
    unsigned setupHeaderSize;
    u_int32_t identField;

    parseVorbisOrTheoraConfigStr(configStr,
                                 identificationHeader, identificationHeaderSize,
                                 commentHeader, commentHeaderSize,
                                 setupHeader, setupHeaderSize,
                                 identField);

    VorbisAudioRTPSink *resultSink
            = new VorbisAudioRTPSink(env, RTPgs, rtpPayloadFormat, rtpTimestampFrequency,
                                     numChannels,
                                     identificationHeader, identificationHeaderSize,
                                     commentHeader, commentHeaderSize,
                                     setupHeader, setupHeaderSize,
                                     identField);
    delete[] identificationHeader;
    delete[] commentHeader;
    delete[] setupHeader;

    return resultSink;
}

VorbisAudioRTPSink
::VorbisAudioRTPSink(UsageEnvironment &env, Groupsock *RTPgs, u_int8_t rtpPayloadFormat,
                     u_int32_t rtpTimestampFrequency, unsigned numChannels,
                     u_int8_t *identificationHeader, unsigned identificationHeaderSize,
                     u_int8_t *commentHeader, unsigned commentHeaderSize,
                     u_int8_t *setupHeader, unsigned setupHeaderSize,
                     u_int32_t identField)
        : AudioRTPSink(env, RTPgs, rtpPayloadFormat, rtpTimestampFrequency, "VORBIS", numChannels),
          fIdent(identField), fFmtpSDPLine(NULL) {
    if (identificationHeaderSize >= 28) {
        // Get the 'bitrate' values from this header, and use them to set our estimated bitrate:
        u_int32_t val;
        u_int8_t *p;

        p = &identificationHeader[16];
        val = ((p[3] * 256 + p[2]) * 256 + p[1]) * 256 + p[0]; // i.e., little-endian
        int bitrate_maximum = (int) val;
        if (bitrate_maximum < 0) bitrate_maximum = 0;

        p = &identificationHeader[20];
        val = ((p[3] * 256 + p[2]) * 256 + p[1]) * 256 + p[0]; // i.e., little-endian
        int bitrate_nominal = (int) val;
        if (bitrate_nominal < 0) bitrate_nominal = 0;

        p = &identificationHeader[24];
        val = ((p[3] * 256 + p[2]) * 256 + p[1]) * 256 + p[0]; // i.e., little-endian
        int bitrate_minimum = (int) val;
        if (bitrate_minimum < 0) bitrate_minimum = 0;

        int bitrate
                = bitrate_nominal > 0 ? bitrate_nominal
                                      : bitrate_maximum > 0 ? bitrate_maximum
                                                            : bitrate_minimum > 0 ? bitrate_minimum
                                                                                  : 0;
        if (bitrate > 0) estimatedBitrate() = ((unsigned) bitrate) / 1000;
    }

    // Generate a 'config' string from the supplied configuration headers:
    char *base64PackedHeaders
            = generateVorbisOrTheoraConfigStr(identificationHeader, identificationHeaderSize,
                                              commentHeader, commentHeaderSize,
                                              setupHeader, setupHeaderSize,
                                              identField);
    if (base64PackedHeaders == NULL) return;

    // Then use this 'config' string to construct our "a=fmtp:" SDP line:
    unsigned fmtpSDPLineMaxSize = 50 + strlen(base64PackedHeaders); // 50 => more than enough space
    fFmtpSDPLine = new char[fmtpSDPLineMaxSize];
    sprintf(fFmtpSDPLine, "a=fmtp:%d configuration=%s\r\n", rtpPayloadType(), base64PackedHeaders);
    delete[] base64PackedHeaders;
}

VorbisAudioRTPSink::~VorbisAudioRTPSink() {
    delete[] fFmtpSDPLine;
}

char const *VorbisAudioRTPSink::auxSDPLine() {
    return fFmtpSDPLine;
}

void VorbisAudioRTPSink
::doSpecialFrameHandling(unsigned fragmentationOffset,
                         unsigned char *frameStart,
                         unsigned numBytesInFrame,
                         struct timeval framePresentationTime,
                         unsigned numRemainingBytes) {
    // Set the 4-byte "payload header", as defined in RFC 5215, section 2.2:
    u_int8_t header[4];

    // The first three bytes of the header are our "Ident":
    header[0] = fIdent >> 16;
    header[1] = fIdent >> 8;
    header[2] = fIdent;

    // The final byte contains the "F", "VDT", and "numPkts" fields:
    u_int8_t F; // Fragment type
    if (numRemainingBytes > 0) {
        if (fragmentationOffset > 0) {
            F = 2 << 6; // continuation fragment
        } else {
            F = 1 << 6; // start fragment
        }
    } else {
        if (fragmentationOffset > 0) {
            F = 3 << 6; // end fragment
        } else {
            F = 0 << 6; // not fragmented
        }
    }
    u_int8_t const VDT = 0 << 4; // Vorbis Data Type (always a "Raw Vorbis payload")
    u_int8_t numPkts = F == 0 ? (numFramesUsedSoFar() + 1) : 0; // set to 0 when we're a fragment
    header[3] = F | VDT | numPkts;

    setSpecialHeaderBytes(header, sizeof header);

    // There's also a 2-byte 'frame-specific' header: The length of the Vorbis data:
    u_int8_t frameSpecificHeader[2];
    frameSpecificHeader[0] = numBytesInFrame >> 8;
    frameSpecificHeader[1] = numBytesInFrame;
    setFrameSpecificHeaderBytes(frameSpecificHeader, 2);

    // Important: Also call our base class's doSpecialFrameHandling(),
    // to set the packet's timestamp:
    MultiFramedRTPSink::doSpecialFrameHandling(fragmentationOffset,
                                               frameStart, numBytesInFrame,
                                               framePresentationTime,
                                               numRemainingBytes);
}

Boolean VorbisAudioRTPSink::frameCanAppearAfterPacketStart(unsigned char const * /*frameStart*/,
                                                           unsigned /*numBytesInFrame*/) const {
    // We allow more than one frame to be packed into an outgoing RTP packet, but no more than 15:
    return numFramesUsedSoFar() <= 15;
}

unsigned VorbisAudioRTPSink::specialHeaderSize() const {
    return 4;
}

unsigned VorbisAudioRTPSink::frameSpecificHeaderSize() const {
    return 2;
}


////////// generateVorbisOrTheoraConfigStr() implementation //////////

char *
generateVorbisOrTheoraConfigStr(u_int8_t *identificationHeader, unsigned identificationHeaderSize,
                                u_int8_t *commentHeader, unsigned commentHeaderSize,
                                u_int8_t *setupHeader, unsigned setupHeaderSize,
                                u_int32_t identField) {
    // First, count how many headers (<=3) are included, and how many bytes will be used
    // to encode these headers' sizes:
    unsigned numHeaders = 0;
    unsigned sizeSize[2]; // The number of bytes used to encode the lengths of the first two headers (but not the length of the 3rd)
    sizeSize[0] = sizeSize[1] = 0;
    if (identificationHeaderSize > 0) {
        sizeSize[numHeaders++] =
                identificationHeaderSize < 128 ? 1 : identificationHeaderSize < 16384 ? 2 : 3;
    }
    if (commentHeaderSize > 0) {
        sizeSize[numHeaders++] = commentHeaderSize < 128 ? 1 : commentHeaderSize < 16384 ? 2 : 3;
    }
    if (setupHeaderSize > 0) {
        ++numHeaders;
    } else {
        sizeSize[1] = 0; // We have at most two headers, so the second one's length isn't encoded
    }
    if (numHeaders == 0) return NULL; // With no headers, we can't set up a configuration
    if (numHeaders == 1) sizeSize[0] = 0; // With only one header, its length isn't encoded

    // Then figure out the size of the packed configuration headers, and allocate space for this:
    unsigned length = identificationHeaderSize + commentHeaderSize + setupHeaderSize;
    // The "length" field in the packed headers
    if (length > (unsigned) 0xFFFF) return NULL; // too big for a 16-bit field; we can't handle this
    unsigned packedHeadersSize
            = 4 // "Number of packed headers" field
              + 3 // "ident" field
              + 2 // "length" field
              + 1 // "n. of headers" field
              + sizeSize[0] + sizeSize[1] // "length1" and "length2" (if present) fields
              + length;
    u_int8_t *packedHeaders = new u_int8_t[packedHeadersSize];
    if (packedHeaders == NULL) return NULL;

    // Fill in the 'packed headers':
    u_int8_t *p = packedHeaders;
    *p++ = 0;
    *p++ = 0;
    *p++ = 0;
    *p++ = 1; // "Number of packed headers": 1
    *p++ = identField >> 16;
    *p++ = identField >> 8;
    *p++ = identField; // "Ident" (24 bits)
    *p++ = length >> 8;
    *p++ = length; // "length" (16 bits)
    *p++ = numHeaders - 1; // "n. of headers"
    if (numHeaders > 1) {
        // Fill in the "length1" header:
        unsigned length1 =
                identificationHeaderSize > 0 ? identificationHeaderSize : commentHeaderSize;
        if (length1 >= 16384) {
            *p++ = 0x80; // flag, but no more, because we know length1 <= 32767
        }
        if (length1 >= 128) {
            *p++ = 0x80 | ((length1 & 0x3F80) >> 7); // flag + the second 7 bits
        }
        *p++ = length1 & 0x7F; // the low 7 bits

        if (numHeaders > 2) { // numHeaders == 3
            // Fill in the "length2" header (for the 'Comment' header):
            unsigned length2 = commentHeaderSize;
            if (length2 >= 16384) {
                *p++ = 0x80; // flag, but no more, because we know length2 <= 32767
            }
            if (length2 >= 128) {
                *p++ = 0x80 | ((length2 & 0x3F80) >> 7); // flag + the second 7 bits
            }
            *p++ = length2 & 0x7F; // the low 7 bits
        }
    }
    // Copy each header:
    if (identificationHeader != NULL) memmove(p, identificationHeader, identificationHeaderSize);
    p += identificationHeaderSize;
    if (commentHeader != NULL) memmove(p, commentHeader, commentHeaderSize);
    p += commentHeaderSize;
    if (setupHeader != NULL) memmove(p, setupHeader, setupHeaderSize);

    // Having set up the 'packed configuration headers', Base-64-encode this, for our result:
    char *base64PackedHeaders = base64Encode((char const *) packedHeaders, packedHeadersSize);
    delete[] packedHeaders;

    return base64PackedHeaders;
}
